#define __CL_ENABLE_EXCEPTIONS
#include "init_opencl.hpp"
#include "matrix_operations.hpp"
#include <time.h>
#include <fstream>
#include <iostream>
#include <CL/cl.hpp>

float * findProductCsrOCLTwoDev(OpenCLEnv openCLEnv, MatrixOperations matrix, MatrixOperations v, int nrTimes) {
	clock_t init, final;
	int rows = matrix.getNumRows();
	int columns = matrix.getNumCols();
	int nonZeros = matrix.getNumNonZeros();
	float * mValues = matrix.getCooPackValues();
	float * vValues = (float*) v.getMatrix();
	//	int * rowIdx = matrix.getCooPackRowIdx();
	int * colIdx = matrix.getCooPackColIdx();
	int * rowPtr = matrix.getCsrRowPtr();

	float * answer = new float[rows];

	cl::Context context = openCLEnv.getContext();
	cl::CommandQueue queue = openCLEnv.getQueue();
	cl::CommandQueue queue2 = openCLEnv.getQueue2();

	//for(int i = 0; i < 11; i++)
	//	cout << mValues[i] << endl;
	//cout << "---------------"<< endl;
	//for(int i = 0; i < 11; i++)
	//	cout << colIdx[i] << endl;
	//cout << "---------------"<< endl;
	//for(int i = 0; i < 6; i++)
	//	cout << rowPtr[i] << endl;
	//cout << "---------------"<< endl;

	int firstNrRows = 0;
	int secondNrRows = 0;

	firstNrRows = rows / 2 + 1;
	secondNrRows = rows - firstNrRows;	

	float * answer1 = new float[firstNrRows];
	float * answer2 = new float[secondNrRows];

	int val1Length = rowPtr[firstNrRows];
	int val2Length = nonZeros - rowPtr[firstNrRows];
	float * mValues1 = new float[val1Length];
	float * mValues2 = new float[val2Length];
	int * colIdx1 = new int[val1Length];
	int * colIdx2 = new int[val2Length];
	int * rowPtr1 = new int[firstNrRows + 1];
	int * rowPtr2 = new int[secondNrRows + 1];

	int j = 0;
	for(int i = 0; i < rowPtr[firstNrRows]; i++) {
		mValues1[i] = mValues[j];
		colIdx1[i] = colIdx[j];
		j++;
	}

	//cout << "ptr " << rowPtr[rows];
	//cout << "val1l: " << val1Length << endl;
	//cout << "val2l: " << val2Length << endl;
	for(int i = 0; i < val2Length; i++) {
		mValues2[i] = mValues[j];
		colIdx2[i] = colIdx[j];
		j++;
	}

	j = 0;
	for(int i = 0; i < firstNrRows + 1; i++) {
		rowPtr1[i] = rowPtr[j];
		j++;
	}

	j--;
	for(int i = 0; i < secondNrRows + 1; i++) {
		rowPtr2[i] = rowPtr[j];
		j++;
	}

	//cout << "val1l: " << val1Length << endl;
	//cout << "val2l: " << val2Length << endl;
	//cout << "colidx1-0:" << colIdx1[0] << endl;	
	//for(int i = 0; i < val1Length; i++)
	//	cout << mValues1[i] << endl;
	//for(int i = 0; i < val2Length; i++)
	//	cout << mValues2[i] << endl;
	//cout << "---------------"<< endl;
	//for(int i = 0; i < val1Length; i++) {
	//	cout << colIdx1[i] << endl;
	//}	
	//for(int i = 0; i < val2Length; i++) {
	//	cout << colIdx2[i] << endl;
	//}

	//cout << "---------------"<< endl;
	//for(int i = 0; i < firstNrRows + 1; i++)
	//	cout << rowPtr1[i] << endl;
	//cout << "---------------"<< endl;
	//for(int i = 0; i < secondNrRows + 1; i++)
	//	cout << rowPtr2[i] << endl;
	//cout << "---------------"<< endl;


	// Read source file
	ifstream sourceFile("csr_opencl_twodev.cl");
	string sourceCode(
		istreambuf_iterator<char>(sourceFile),
		(istreambuf_iterator<char>()));
	cl::Program::Sources source(1, make_pair(sourceCode.c_str(), sourceCode.length()+1));

	// Make program of the source code in the context
	cl::Program program = cl::Program(context, source);

	// Build program for these specific devices
	program.build(openCLEnv.getDevices());

	cl::Kernel kernel(program, "csr_opencl_twodev");
	cl::Kernel kernel2(program, "csr_opencl_twodev");

	cl::Buffer bufferValues1 = cl::Buffer(context, CL_MEM_READ_ONLY, val1Length * sizeof(float));
	cl::Buffer bufferVector1 = cl::Buffer(context, CL_MEM_READ_ONLY, columns * sizeof(float));
	cl::Buffer bufferColIdx1 = cl::Buffer(context, CL_MEM_READ_ONLY, val1Length * sizeof(int));
	cl::Buffer bufferRowPtr1 = cl::Buffer(context, CL_MEM_READ_ONLY, (firstNrRows + 1) * sizeof(int));
	cl::Buffer bufferAnswer1 = cl::Buffer(context, CL_MEM_WRITE_ONLY, firstNrRows * sizeof(float));

	cl::Buffer bufferValues2 = cl::Buffer(context, CL_MEM_READ_ONLY, val2Length * sizeof(float));
	cl::Buffer bufferVector2 = cl::Buffer(context, CL_MEM_READ_ONLY, columns * sizeof(float));
	cl::Buffer bufferColIdx2 = cl::Buffer(context, CL_MEM_READ_ONLY, val2Length * sizeof(int));
	cl::Buffer bufferRowPtr2 = cl::Buffer(context, CL_MEM_READ_ONLY, (secondNrRows + 1) * sizeof(int));
	cl::Buffer bufferAnswer2 = cl::Buffer(context, CL_MEM_WRITE_ONLY, secondNrRows * sizeof(float));

	queue.enqueueWriteBuffer(bufferValues1, CL_TRUE, 0, val1Length * sizeof(float), mValues1);
	queue.enqueueWriteBuffer(bufferVector1, CL_TRUE, 0, columns * sizeof(float), vValues);
	queue.enqueueWriteBuffer(bufferColIdx1, CL_TRUE, 0, val1Length * sizeof(int), colIdx1);
	queue.enqueueWriteBuffer(bufferRowPtr1, CL_TRUE, 0, (firstNrRows + 1) * sizeof(int), rowPtr1);

	queue2.enqueueWriteBuffer(bufferValues2, CL_TRUE, 0, val2Length * sizeof(float), mValues2);
	queue2.enqueueWriteBuffer(bufferVector2, CL_TRUE, 0, columns * sizeof(float), vValues);
	queue2.enqueueWriteBuffer(bufferColIdx2, CL_TRUE, 0, val2Length * sizeof(int), colIdx2);
	queue2.enqueueWriteBuffer(bufferRowPtr2, CL_TRUE, 0, (secondNrRows + 1) * sizeof(int), rowPtr2);

	kernel.setArg(0, bufferValues1);
	kernel.setArg(1, bufferVector1);
	kernel.setArg(2, bufferColIdx1);
	kernel.setArg(3, bufferRowPtr1);
	kernel.setArg(4, firstNrRows);
	kernel.setArg(5, bufferAnswer1);
	kernel.setArg(6, 0);

	int offSet = rowPtr1[firstNrRows];
	kernel2.setArg(0, bufferValues2);
	kernel2.setArg(1, bufferVector2);
	kernel2.setArg(2, bufferColIdx2);
	kernel2.setArg(3, bufferRowPtr2);
	kernel2.setArg(4, secondNrRows);
	kernel2.setArg(5, bufferAnswer2);
	kernel2.setArg(6, offSet);

	cl::NDRange global1(firstNrRows);
	cl::NDRange local1(1);
	cl::NDRange global2(secondNrRows);
	cl::NDRange local2(1);
	//cout << "first rows: " << firstNrRows << endl;
	//cout << "second rows: " << secondNrRows << endl;

	init=clock();
	for(int i = 0; i < nrTimes; i++) {
		queue.enqueueNDRangeKernel(kernel, cl::NullRange, global1, local1);
		queue.enqueueReadBuffer(bufferAnswer1, CL_FALSE, 0, firstNrRows * sizeof(float), answer1);
		queue2.enqueueNDRangeKernel(kernel2, cl::NullRange, global2, local2);
		queue2.enqueueReadBuffer(bufferAnswer2, CL_FALSE, 0, secondNrRows * sizeof(float), answer2);
		queue.finish();
        queue2.finish();
	}
	final=clock()-init;

	double time = (double)final / ((double)CLOCKS_PER_SEC);
	cout << "Calculation took: " << time << " seconds. (CSR OpenCL, two devices, running kernel)" << endl;
	cout << "CSR opencl, time per: " << time/nrTimes << endl;


	j = 0;
	for(int i = 0; i < firstNrRows; i++) {
		answer[j] = answer1[i];
		j++;
	}
	for(int i = 0; i < secondNrRows; i++) {
		answer[j] = answer2[i];
		j++;
	}

	return answer;
}



